package rs.udd.web.controller;

import static rs.udd.service.util.SecurityUtil.AGENT_ROLE_ONLY;

import java.util.Date;

import javax.validation.Valid;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import rs.udd.exceptions.MissingMetaDataException;
import rs.udd.exceptions.UddException;
import rs.udd.exceptions.UserAuthenticationCheckException;
import rs.udd.exceptions.password.NewPasswordMismatchException;
import rs.udd.exceptions.password.PasswordMismatchException;
import rs.udd.model.Advertiser;
import rs.udd.model.Agent;
import rs.udd.model.Category;
import rs.udd.service.AgentService;
import rs.udd.service.ContactAgentService;
import rs.udd.service.dto.advertiser.ContactAgentDto;
import rs.udd.service.dto.agent.AgentEditDto;
import rs.udd.service.dto.agent.AgentRegisterDto;
import rs.udd.service.dto.agent.AgentUploadCvDto;
import rs.udd.service.dto.agent.AgentViewDto;
import rs.udd.service.dto.agent.SettingsDto;
import rs.udd.service.dto.user.DeleteUserDto;
import rs.udd.service.dto.user.UserPasswordEditDto;
import rs.udd.service.mappings.AgentMapping;
import rs.udd.service.util.SecurityUtil;
import rs.udd.web.binding.DateBinder;

@Controller
@RequestMapping(value = "/agents")
public class AgentController {

	private static final Logger LOGGER = LogManager.getLogger(AgentController.class);

	@Autowired
	private AgentService agentService;
	
	@Autowired
	private ContactAgentService contactAgentService;

	@Autowired
	private DateBinder dateBinder;

	@InitBinder
	public void initBind(WebDataBinder binder) {
		binder.registerCustomEditor(Date.class, dateBinder);
	}

	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public String handleAddGet(Model model) {
		model.addAttribute("newAgent", new AgentRegisterDto());
		return "agent/registerAgent";
	}

	@RequestMapping(value = "/register", method = RequestMethod.POST)
	public String handleAddPost(@Valid @ModelAttribute("newAgent") AgentRegisterDto agentDto, BindingResult result, Model model) {
		if (result.hasErrors()) {
			LOGGER.error("Binding error," + result);
			return "agent/registerAgent";
		}

		try {
			LOGGER.info("Registering agent...");
			agentService.register(agentDto);

			LOGGER.info("Agent saved successfully.");

			// when posting a successful form to controller, always do redirect after. google post-get-redirect for a reason.
			return "redirect:/home";

		} catch (UddException e) {
			LOGGER.error("Error while persisting agent.", e);
			// TODO add some message to client
			return "agent/registerAgent";
		}

	}

	@RequestMapping(value = "/view/{username}", method = RequestMethod.GET)
	public String handleViewGet(@PathVariable String username, Model model) {
		try {
			Agent agent = agentService.findAgentByUsernameForView(username);

			if (agent == null) {
				// TODO add some message to client and return
				String message = "Agent with username '" + username + "' doesn't exist.";
				LOGGER.error(message);
				model.addAttribute("message", message);
				return "error/error";
			}

			AgentViewDto dto = AgentMapping.view(agent);
			model.addAttribute("viewedAgent", dto);

			boolean agentContacted = contactAgentService.checkIfAgentContacted(agent.getId());
			model.addAttribute("agentContacted", agentContacted);
			model.addAttribute("contactAgentDto", new ContactAgentDto());
			
			model.addAttribute("deleteUserDto", new DeleteUserDto());
			
			return "agent/viewAgent";

		} catch (UddException e) {
			LOGGER.error("Error while requesting edit agent.", e);
			model.addAttribute("message", e.getMessage());
			return "error/error";
		}
	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/edit", method = RequestMethod.GET)
	public String handleEditGet(Model model, Authentication authentication) throws UddException {

		Agent agent = SecurityUtil.getLoggedUser(Agent.class);
		agent = agentService.findOne(agent.getId());

		AgentViewDto editAgentDto = AgentMapping.viewToEdit(agent);
		model.addAttribute("editedAgent", editAgentDto);

		return "agent/editAgent";
	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/edit", method = RequestMethod.POST)
	public String handleEditPost(@Valid @ModelAttribute("editedAgent") AgentEditDto dto, BindingResult result, Model model,
			Authentication authentication) throws UddException {

		Agent loggedAgent = SecurityUtil.getLoggedUser(Agent.class);
		loggedAgent = agentService.findOne(loggedAgent.getId());
		
		if (result.hasErrors()) {
			LOGGER.error("Binding error," + result);
			return "agent/editAgent";
		}

		try {
			LOGGER.info("Editing agent...");
			agentService.edit(dto);

			LOGGER.info("Agent saved successfully.");

			// when posting a successful form to controller, always do redirect after. google post-get-redirect for a reason.
			return "redirect:/home";
		} catch (UddException e) {
			LOGGER.error("Error while requesting edit agent.", e);
			model.addAttribute("message", e.getMessage());
			return "error/error";
		}
	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/upload-cv", method = RequestMethod.GET)
	public String prepareUploadCv(Model model) {
		LOGGER.info("Prepraing upload cv...");
		model.addAttribute("agentUploadCvDto", new AgentUploadCvDto());
		return "agent/uploadCv";
	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/upload-cv", method = RequestMethod.POST)
	public String uploadCv(@Valid @ModelAttribute("agentUploadCvDto") AgentUploadCvDto agentUploadCvDto, BindingResult result, Model model) {

		if (result.hasErrors()) {
			LOGGER.error("Binding error: " + result);
			return "agent/uploadCv";
		}

		try {
			agentService.uploadCv(agentUploadCvDto);
			return "redirect:/home";
		} catch (MissingMetaDataException e) {
			LOGGER.error("Error", e);
			return "agent/uploadCv";	// TODO: test this and add form:error tag on jsp
		} catch (UddException e) {
			LOGGER.error("Error", e);
			return "error/error";
		}

	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/delete-cv", method = RequestMethod.GET)
	public String deleteCv() {
		try {
			agentService.deleteCv();
			return "redirect:/home";
		} catch (UddException e) {
			LOGGER.error("Error.", e);
			return "error/error";
		}
	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/edit-password", method = RequestMethod.GET)
	public String handleEditPasswordGet(Model model) throws UddException {
		
		Agent loggedAgent = SecurityUtil.getLoggedUser(Agent.class);
		loggedAgent = agentService.findOne(loggedAgent.getId());
		
		UserPasswordEditDto editPasswordDto = AgentMapping.editPassword(loggedAgent);
		model.addAttribute("editPasswordDto", editPasswordDto);
		
		return "agent/editPassword";
	}
	
	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/edit-password", method = RequestMethod.POST)
	public String handleEditPasswordPost(@Valid @ModelAttribute("editPasswordDto") UserPasswordEditDto dto, BindingResult result, Model model,
			Authentication authentication) throws UddException {

		Agent loggedAgent = SecurityUtil.getLoggedUser(Agent.class);
		loggedAgent = agentService.findOne(loggedAgent.getId());
		
		if (result.hasErrors()) {
			LOGGER.error("Binding error: " + result);
			return "agent/editPassword";
		}

		try {
			LOGGER.info("Changing agent's password...");
			boolean passwordChanged = agentService.editPassword(dto);
			if (passwordChanged) {
				LOGGER.info("Agent's password edited successfully.");
				// when posting a successful form to controller, always do redirect after. google post-get-redirect for a reason.
				return "redirect:/home";
			} else {
				result.reject("oldPassword", "Old password not valid.");
				return "agent/editPassword";
			}
			
		} catch (UserAuthenticationCheckException e) {
			LOGGER.error("Error while requesting edit agent.", e);
			model.addAttribute("message", "Requested editing for agent that is not currently logged in.");
			return "error/error";
		} catch (PasswordMismatchException e) {
			LOGGER.error(e.getMessage());
			if (e instanceof NewPasswordMismatchException) {
				result.reject("newPassword", e.getMessage());
			}

			return "agent/editAgent";

		} catch (UddException e) {
			LOGGER.error("Error while requesting edit password for agent.", e);
			model.addAttribute("message", e.getMessage());
			return "error/error";
		}
	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/settings", method = RequestMethod.GET)
	public String editSettingsGet(Model model) {

		LOGGER.info("Get settings");
		try {
			SettingsDto dto = agentService.prepareSettingsDto();
			model.addAttribute("dto", dto);
			return "/agent/settings";
		} catch (UddException e) {
			LOGGER.error("Error while requesting edit settings for agent.", e);
			model.addAttribute("message", e.getMessage());
			return "error/error";
		}
	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/settings/showOrHideCategory", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
	public @ResponseBody Category showOrHideCategory(@RequestParam(value = "id") Long id) {
		LOGGER.info("Show or hide category with id " + id);
		try {
			Category category = agentService.showOrHideCategory(id);
			return category;
		} catch (UddException e) {
			LOGGER.error("Error while showing or hidding category.", e);
			return null;
		}
	}

	@PreAuthorize(AGENT_ROLE_ONLY)
	@RequestMapping(value = "/settings/showOrHideAdvertiser", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
	public @ResponseBody Advertiser showOrHideAdvertiser(@RequestParam(value = "id") Long id) {
		LOGGER.info("Show or hide advertiser with id " + id);
		try {
			Advertiser advertiser = agentService.showOrHideAdvertiser(id);
			return advertiser;
		} catch (UddException e) {
			LOGGER.error("Error while showing or hidding advertiser.", e);
			return null;
		}
	}

	@PreAuthorize(SecurityUtil.ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/view/{username}/contact", method = RequestMethod.POST)
	public String contactAgent(@PathVariable("username") String username, @ModelAttribute("contactAgentDto") ContactAgentDto contactAgentDto) {
		contactAgentService.contact(username, contactAgentDto);
		return "redirect:/home";
	}

}
